package com.sdg.server.service;

import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Sorts;
import com.sdg.server.model.recom.Recommendation;
import com.sdg.server.model.request.*;
import org.bson.Document;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.index.query.FuzzyQueryBuilder;
import org.elasticsearch.index.query.MoreLikeThisQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

@Service
public class RecommenderService {
    @Autowired
    private MongoClient mongoClient;
    @Autowired
    private TransportClient esClient;
    // 混合推荐中CF的比例
    private static Double CF_RATING_FACTOR = 0.3;
    private static Double CB_RATING_FACTOR = 0.3;
    private static Double SR_RATING_FACTOR = 0.4;

    /**
     * @param request
     * @return
     */
    public List<Recommendation> getHybridRecommendations(MovieHybridRecommendationRequest request) {
        //根据用户id
        return findHybridRecommendations(request.getMid(), request.getSum());
    }

    /**
     * 解析推荐列表
     *
     * @param document
     * @param maxItems
     * @return
     */
    private List<Recommendation> parseRecs(Document document, int maxItems) {
        //创建一arrayList 存放推荐的电影
        List<Recommendation> recommendations = new ArrayList<>();
        //如果说没有查到，就直接放返回
        if (null == document || document.isEmpty())
            return recommendations;
        //从document 中取出值，然后在便利，封住到List 中
        ArrayList<Document> recs = document.get("recommendations", ArrayList.class);
        if(recs ==null){
             recs = document.get("recs", ArrayList.class);
        }
        System.out.println(recs);
        for (Document recDoc : recs) {
            //（电影的id,分值）
            recommendations.add(new Recommendation(recDoc.getInteger("rid"), recDoc.getDouble("r")));
        }
        //list 中的数据 按照分值进行排序
        Collections.sort(recommendations, new Comparator<Recommendation>() {
            @Override
            public int compare(Recommendation o1, Recommendation o2) {
                return o1.getScore() > o2.getScore() ? -1 : 1;
            }
        });
//三元表达式子
        return recommendations.subList(0, maxItems > recommendations.size() ? recommendations.size() : maxItems);
    }

    /**
     * 协同过滤推荐【电影相似性】
     *
     * @param mid
     * @param maxItems
     * @return
     */
    public List<Recommendation> findMovieCFRecs(int mid, int maxItems) {
        System.out.println("---------------------------------");
        MongoCollection<Document> movieRecsCollection = mongoClient.getDatabase(Constant.MONGODB_DATABASE).getCollection(Constant.MONGODB_MOVIE_RECS_COLLECTION);
        //从电影相似度度矩阵中，根据传过来的电影的id 去查出与之相似的电影
        Document movieRecs = movieRecsCollection.find(new Document("mid", mid)).first();
        System.out.println(movieRecs);
        //解析推荐列表 返回的是list
        return parseRecs(movieRecs, maxItems);
    }

    /**
     * 混合推荐算法
     *
     * @param productId 电影的id
     * @param maxItems  推荐的数量
     * @return
     */
    private List<Recommendation> findHybridRecommendations(int productId, int maxItems) {
        //
        List<Recommendation> hybridRecommendations = new ArrayList<>();
        //1.协同过滤推荐【电影相似性】
        List<Recommendation> cfRecs = findMovieCFRecs(productId, maxItems);
        for (Recommendation recommendation : cfRecs) {
            hybridRecommendations.add(new Recommendation(recommendation.getMid(), recommendation.getScore() * CF_RATING_FACTOR));
        }
        //2.从es中查询
        List<Recommendation> cbRecs = findContentBasedMoreLikeThisRecommendations(productId, maxItems);
        for (Recommendation recommendation : cbRecs) {
            hybridRecommendations.add(new Recommendation(recommendation.getMid(), recommendation.getScore() * CB_RATING_FACTOR));
        }
        //3.实时推荐
        List<Recommendation> streamRecs = findStreamRecs(productId, maxItems);
        for (Recommendation recommendation : streamRecs) {
            hybridRecommendations.add(new Recommendation(recommendation.getMid(), recommendation.getScore() * SR_RATING_FACTOR));
        }
        //4.然后在按照分值进行排序
        Collections.sort(hybridRecommendations, new Comparator<Recommendation>() {
            @Override
            public int compare(Recommendation o1, Recommendation o2) {
                return o1.getScore() > o2.getScore() ? -1 : 1;
            }
        });
        //返回最后总的推荐结果
        return hybridRecommendations.subList(0, maxItems > hybridRecommendations.size() ? hybridRecommendations.size() : maxItems);
    }

    /**
     * 基于内容的推荐算法
     *
     * @param mid
     * @param maxItems
     * @return
     */
    private List<Recommendation> findContentBasedMoreLikeThisRecommendations(int mid, int maxItems) {
        MoreLikeThisQueryBuilder query = QueryBuilders.moreLikeThisQuery(new String[]{"id"},
                new String[]{"name", "descri", "genres", "actors", "directors", "tags"},
                new MoreLikeThisQueryBuilder.Item[]{new MoreLikeThisQueryBuilder.Item(Constant.ES_INDEX, Constant.ES_MOVIE_TYPE, String.valueOf(mid))});

        return parseESResponse(esClient.prepareSearch().setQuery(query).setSize(maxItems).execute().actionGet());
    }

    /**
     * 解析es 返回的内容
     *
     * @param response
     * @return
     */
    private List<Recommendation> parseESResponse(SearchResponse response) {
        List<Recommendation> recommendations = new ArrayList<>();
        for (SearchHit hit : response.getHits()) {
            recommendations.add(new Recommendation((int) hit.getSourceAsMap().get("mid"), (double) hit.getScore()));
        }
        return recommendations;
    }

    /**
     * 实时推荐
     *
     * @param uid
     * @param maxItems
     * @return
     */
    public List<Recommendation> findStreamRecs(int uid, int maxItems) {
        MongoCollection<Document> streamRecsCollection = mongoClient.getDatabase(Constant.MONGODB_DATABASE).getCollection(Constant.MONGODB_STREAM_RECS_COLLECTION);
        Document streamRecs = streamRecsCollection.find(new Document("uid", uid)).first();
        System.out.println(streamRecs);
        return parseRecs(streamRecs, maxItems);
    }

    /**
     * 获取每种类别的topn
     *
     * @param request
     * @return
     */
    public List<Recommendation> getTopGenresRecommendations(TopGenresRecommendationRequest request) {
        Document genresTopMovies = mongoClient.getDatabase(Constant.MONGODB_DATABASE).getCollection(Constant.MONGODB_GENRES_TOP_MOVIES_COLLECTION)
               // .findOne();
                //.find(Filters.eq("genres", request.getGenres())).first();
                .find(Filters.eq("genres", "Comedy")).first();
                //.find().first();
        return parseRecs(genresTopMovies, request.getSum());
    }

    /**
     * 用户的
     *
     * @param request
     * @return
     */
    public List<Recommendation> getCollaborativeFilteringRecommendations(UserRecommendationRequest request) {
        return findUserCFRecs(request.getUid(), request.getSum());
    }

    /**
     * 协同过滤推荐【用户电影矩阵】
     *
     * @param uid
     * @param maxItems
     * @return
     */
    private List<Recommendation> findUserCFRecs(int uid, int maxItems) {
        MongoCollection<Document> movieRecsCollection = mongoClient.getDatabase(Constant.MONGODB_DATABASE).getCollection(Constant.MONGODB_USER_RECS_COLLECTION);
        Document userRecs = movieRecsCollection.find(new Document("uid", uid)).first();
        return parseRecs(userRecs, maxItems);
    }

    public List<Recommendation> getHotRecommendations(HotRecommendationRequest request) {
        // 获取热门电影的条目
        MongoCollection<Document> rateMoreMoviesRecentlyCollection = mongoClient.getDatabase(Constant.MONGODB_DATABASE).getCollection(Constant.MONGODB_RATE_MORE_MOVIES_RECENTLY_COLLECTION);
        FindIterable<Document> documents = rateMoreMoviesRecentlyCollection.find().sort(Sorts.descending("yeahmonth")).limit(request.getSum());

        List<Recommendation> recommendations = new ArrayList<>();
        for (Document document : documents) {
            recommendations.add(new Recommendation(document.getInteger("mid"), 0D));
        }
        return recommendations;
    }

    /**
     * 获取投票最多的电影
     *
     * @param request
     * @return
     */
    public List<Recommendation> getRateMoreRecommendations(RateMoreRecommendationRequest request) {

        // 获取评分最多电影的条目
        MongoCollection<Document> rateMoreMoviesCollection = mongoClient.getDatabase(Constant.MONGODB_DATABASE).getCollection(Constant.MONGODB_RATE_MORE_MOVIES_COLLECTION);
        FindIterable<Document> documents = rateMoreMoviesCollection.find().sort(Sorts.descending("count")).limit(request.getSum());

        List<Recommendation> recommendations = new ArrayList<>();
        for (Document document : documents) {
            recommendations.add(new Recommendation(document.getInteger("mid"), 0D));
        }
        return recommendations;
    }

    /**
     * 电影的
     *
     * @param request
     * @return
     */
    public List<Recommendation> getCollaborativeFilteringRecommendations(MovieRecommendationRequest request) {
        return findMovieCFRecs(request.getMid(), request.getSum());
    }

    /**
     * 模糊查询
     *
     * @param request
     * @return
     */
    public List<Recommendation> getContentBasedSearchRecommendations(SearchRecommendationRequest request) {
        return findContentBasedSearchRecommendations(request.getText(), request.getSum());
    }

    /**
     * 全文检索
     *
     * @param text
     * @param maxItems
     * @return
     */
    private List<Recommendation> findContentBasedSearchRecommendations(String text, int maxItems) {
        MultiMatchQueryBuilder query = QueryBuilders.multiMatchQuery(text, "name", "descri");
        return parseESResponse(esClient.prepareSearch().setIndices(Constant.ES_INDEX).setTypes(Constant.ES_MOVIE_TYPE).setQuery(query).setSize(maxItems).execute().actionGet());
    }

    /**
     * @param request
     * @return
     */
    public List<Recommendation> getContentBasedGenresRecommendations(SearchRecommendationRequest request) {
        FuzzyQueryBuilder query = QueryBuilders.fuzzyQuery("genres", request.getText());
        return parseESResponse(esClient.prepareSearch().setIndices(Constant.ES_INDEX).setTypes(Constant.ES_MOVIE_TYPE).setQuery(query).setSize(request.getSum()).execute().actionGet());
    }

    /**
     * 离线推荐
     *
     * @param movieHybridRecommendationRequest
     * @return
     */
    public List<Recommendation> getOffLineRecommendations(MovieHybridRecommendationRequest movieHybridRecommendationRequest) {


        return null;
    }
}
